##
## a lot of this is basically pseudo-code...
##

from nevow import tags
import time
import calendar
from activist import errors
from activist.Config import Config

def formatTime(tm,fmt=None):
    if not fmt:
        fmt = '%A, %B %d %Y @ %H:%M %Z'
    return time.strftime(fmt,tm)

def fromImportListingDetail(d):
    if d['repeat-period'] == 'daily' or d['repeat-period'] == '':
        if d['time-start'] == '': d['time-start'] = "00:00:00"
        d['dateline'] = d['date-start'] + ' ' + d['time-start']
        startseconds = None
        endseconds = None
        if d['time-start'] != '':
            startseconds = int(d['time-start'][:2])*3600 + int(d['time-start'][3:5])*60
        if d['time-end'] != '':
            endseconds = int(d['time-end'][:2])*3600 + int(d['time-end'][3:5])*60

        try:
            if endseconds is None or startseconds is None:
                return Moment(time.mktime(time.strptime(d['dateline'],'%Y-%m-%d %H:%M:%S')))

            duration = endseconds - startseconds
            return TimeRange(time.mktime(time.strptime(d['dateline'],'%Y-%m-%d %H:%M:%S')), duration)
        except ValueError:
            raise
            return None

    elif d['repeat-period'] == 'annually':
        print "ANNUAL repeat; skipping"
        return None

    elif d['repeat-period'] == 'monthly':
        print "MONTHLY REPEAT!"
        day = d['repeat-day'].strip().lower()
        if day == '':
            print d
            raise ValueError("no day!")
            return None
        if d['time-start'] != '':
            (h,m,s) = d['time-start'].split(':')
            start = int(h)*60*60 + int(m)*60 + int(s)
            (h,m,s) = d['time-end'].split(':')
            end = int(h)*60*60 + int(m)*60 + int(s)
            duration = end - start
        else:
            start = 0
            end = 0
            duration = 24*3600

        freq = d['repeat-frequency']
        if freq == '':
            freq = ADJECTIVES[int(d['repeat-number'])]
            print "ADJ:",freq
        print "DAY",day
        datestart = time.strptime("%Y-%m-%d", d['date-start'])
        dateend = time.strptime("%Y-%m-%d", d['date-end'])
        (h,m,s) = d['time-start'].split(':')
        
        realstart = (datestart.tm_year,
                     datestart.tm_mon,
                     datestart.tm_mday,
                     h,
                     m,
                     s,
                     datestart.tm_wday,
                     datestart.tm_yday,
                     datestart.tm_isdst)
        
        realend = (dateend.tm_year,
                   dateend.tm_mon,
                   dateend.tm_mday,
                   h,
                   m,
                   s,
                   dateend.tm_wday,
                   dateend.tm_yday,
                   dateend.tm_isdst)
        
        gen = RepeatGenerateMonthly(freq.strip().lower(), day)
        return Repeating(realstart, duration, gen, realend)
        
    elif d['repeat-period'] == 'weekly':
        print "WEEKLY REPEAT"
        for x in d.keys():
            if x[:6] == 'repeat':
                print x,d[x]

        day = d['repeat-day'].strip().lower()
        monthrepeat = int(d['repeat-number']) - 1
        monthrepeat = ['', 'first', 'second', 'third', 'forth', 'fifth'][monthrepeat]
        datestart = time.strptime("%Y-%m-%d", d['date-start'])
        dateend = time.strptime("%Y-%m-%d", d['date-end'])

        (h,m,s) = d['time-end'].split(':')
        end = int(h)*60*60 + int(m)*60 + int(s)
        (h,m,s) = d['time-start'].split(':')
        start = int(h)*60*60 + int(m)*60 + int(s)
        duration = end - start

        realstart = (datestart.tm_year,
                     datestart.tm_mon,
                     datestart.tm_mday,
                     h,
                     m,
                     s,
                     datestart.tm_wday,
                     datestart.tm_yday,
                     datestart.tm_isdst)
        
        realend = (dateend.tm_year,
                   dateend.tm_mon,
                   dateend.tm_mday,
                   h,
                   m,
                   s,
                   dateend.tm_wday,
                   dateend.tm_yday,
                   dateend.tm_isdst)
        
        generator = RepeatGenerateWeekly(monthrepeat, day)
        return Repeating( realstart, duration, generator, realend)
        

    else:
        print "REPEAT",d
        raise "uh oh!" + d['repeat-period']
        if period == 'weekly':
            pass
            

    raise "can't parse time"
    return None

def parseTime(arg):
    try:
        return time.strptime(arg, '%B %d, %Y')
    except ValueError:
        raise errors.TimeParseError("Couldn't understand time; try something like \"August 12, 3004\".");

def parseDuration(args):
    duration = 0
    bits = ''

    if args.has_key('radio_once') and args['radio_once'][0]:
        bits = args['duration_once'][0].lower().split()
    else:
        bits = args['recurring_duration'][0].lower().split()
        
    for x in range(0,len(bits)):
        if x > 0 and (bits[x] == 'hours' or bits[x] == 'hour'):
            hours = None
            try:
                hours = int(bits[x-1])
                hours = hours * 3600
            except ValueError:
                pass
            if bits[x-1] in ['zero','one','two','three','four','five','six','seven','eight','nine','ten']:
                hours = 0
                for y in ['zero','one','two','three','four','five','six','seven','eight','nine','ten']:
                    if bits[x-1] == y:
                        break
                    else:
                        hours = hours + 3600
            if hours is not None:
                duration = duration + hours

        elif x > 0 and (bits[x] == 'minutes'):
            try:
                minutes = int(bits[x-1]) * 60
                duration = duration + minutes
            except ValueError:
                pass

    if duration == 0:
        whole = args['duration_once'][0].lower().strip()
        if whole == "all day":
            duration = 24*3600
        elif whole == "afternoon" or whole == "morning":
                                    # FIXME?
            duration = 6*3600

    if duration == 0:
        raise errors.TimeParseError("Couldn't understand duration; try something like \"6 hours and 15 minutes\"");
    return duration


def fromEventArgs(args):
    if args.has_key('radio_date') and args['radio_date'][0] == 'recurring':
        start = parseTime(args['recurring_start'][0])
        stop = parseTime(args['recurring_stop'][0])
                                        # month = ['every', 'monthly']
        month = args['recurring_month'][0].lower().strip()
        
                                        # monthreapeat = ['nothing','first','second','third','forth','fifth']
        monthrepeat = args['recurring_monthrepeat'][0].lower().strip()
        if monthrepeat == 'nothing': monthreapeat = ''
        
                                        # ['sunday' ... 'saturday']
        day = args['recurring_day'][0].lower().strip()
        
                                        # a freeform time
        try:
            tm = time.strptime(args['recurring_time'][0].lower().strip(),'%H:%M')
            tm2 = tm
        except ValueError:
            raise errors.TimeParseError("Couldn't undestand time.")

        startday = calendar.weekday(start.tm_year, start.tm_mon, start.tm_mday)
        if startday != DAYS.index(day):
            raise errors.TimeParseError("Start day is not a %s (it is a %s)." % (day, DAYS[startday]))

                                        # we must back up the
                                        # start-time so that
                                        # "getNextAfter(start)"
                                        # returns what the user put as
                                        # the start-time, so must go
                                        # back 0 to 5 weeks, depending
                                        # upon "monthrepeat"
        if monthrepeat != '':
            y = start.tm_year
            m = start.tm_mon
            d = start.tm_mday
            d = d - (7*['nothing','first','second','third','forth','fifth'].index(monthrepeat))
            if d < 0:
                m = m - 1
                if m < 0:
                    m = 12
                    y = y -1
                d = calendar.mdays[m] + d # d is still negative

                                        # is this REALLY the only way to copy date structs?!
            tm = (y,
                     m,
                     d,
                     tm.tm_hour,
                     tm.tm_min,
                     tm.tm_sec,
                     start.tm_wday,
                     start.tm_yday,
                     start.tm_isdst)
        else:
            tm = (start.tm_year,
                  start.tm_mon,
                  start.tm_mday,
                  tm.tm_hour,
                  tm.tm_min,
                  tm.tm_sec,
                  start.tm_wday,
                  start.tm_yday,
                  start.tm_isdst)


                                        # same stuff for stop-day

        stopday = calendar.weekday(stop.tm_year, stop.tm_mon, stop.tm_mday)
        if stopday != DAYS.index(day):
            raise errors.TimeParseError("Stop day is not a %s (it is a %s)." % (day, DAYS[stopday]))
        else:
            tm2 = stop

        duration = parseDuration(args)

        if month == 'every':
            generator = RepeatGenerateWeekly(monthrepeat, day)
        else:
            generator = RepeatGenerateMonthly(monthrepeat, day)

        return Repeating( tm, duration, generator, tm2 )
        
        
        

                                        # parsing for one-time events
    if args.has_key('radio_date') and args['radio_date'][0] == 'once':
        try:
            date = time.strptime(args['date_once'][0],'%B %d %Y')
        except ValueError:
            raise errors.TimeParseError("Couldn't understand date.")
        try:
            tm = time.strptime(args['time_once'][0],'%H:%M')
        except ValueError:
            raise errors.TimeParseError("Couldn't undestand time.")
        date = (date.tm_year,date.tm_mon,date.tm_mday,
                tm.tm_hour,tm.tm_min,tm.tm_sec,
                date.tm_wday,date.tm_yday,date.tm_isdst)

        duration = parseDuration(args)
        
        return TimeRange(time.mktime(date),duration)

    return None

class Time(object):

    def format(self,fmt=None):
        return "[unformatted time]"

    def duration(self):
        return ''

    def timeFits(self,time):
        raise "subclass responsibility"

    def isToday(self):
        raise "subclass responsibility"

    def isValid(self,year,month,day):
        """day may be None, which means is this event possibly in this month?"""
        raise "subclass responsibility"

    def localtime(self):
        raise "subclass responsibility"

    ## other method ideas (might not be needed):

    def timeInRange(self,start,end):
        raise "subclass"

    def closestTimeTo(self,time):
        raise "subclass"

class NoneTime(Time):
    def format(self,fmt=None):
        return ''

class TimeRange(Time):
    """
    this should do way more checking
    """
    
    def __init__(self,start,duration):
        self._start = start
        self._end = start+duration
        self._duration = duration

    def start(self):
        return self._start

    def stop(self):
        return self._start

    def format(self,fmt=None):
        import time
        if not fmt:
            fmt = '%A, %B %d %Y @ %H:%M %Z'
        duration = '%d:%d' % (int(self._duration/3600), int((self._duration%3600)/60))
        return time.strftime(fmt,time.localtime(self._start))

    def formatTextOnly(self,fmt=None):
        import time
        if not fmt:
            fmt = '%A, %B %d %Y @ %H:%M %Z'
        duration = '%d:%d' % (int(self._duration/3600), int((self._duration%3600)/60))
        return time.strftime(fmt,time.localtime(self._start))

    def closestTimeTo(self,w):
        return time.localtime(self._start)
    
    def duration(self):
        hrmin = (int(self._duration/3600), int((self._duration%3600)/60))
        rtn = ''
        if hrmin[0] == 0 or hrmin[0] == 1:
            rtn = rtn + '%d hour' % hrmin[0]
        else:
            rtn = rtn + '%d hours' % hrmin[0]

        if hrmin[1] > 0:
            rtn = rtn + ' and %d minute' % hrmin[1]
            if hrmin[1] != 1:
                rtn = rtn + 's'
        return rtn

    def diffFrom(self,when):
        return when - self._start
    
    def timeFits(self,time):
        return time > self._start and time < self._end

    def gmtime(self):
        return time.gmtime(self._start)

    def localtime(self):
        return time.localtime(self._start)

    def isValid(self,year,month,day):
        return time.localtime(self._start).tm_year == year and time.localtime(self._start).tm_mon == month

    def isToday(self):
        return time.localtime(self._start).tm_mday == time.localtime().tm_mday

    def __eq__(self,other):
        if isinstance(other,Moment):
            return self._start == other._when
        return other._start == other._start

    def __cmp__(self,other):
        if isinstance(other,Moment):
            return cmp(self._start,other._when)
        return cmp(self._start,other._start)

class Moment(Time):

    def __init__(self,when):
        if isinstance(when,Time) or not isinstance(when,float):
            raise errors.IllegalArgument("I require a float")
        if not when:
            raise errors.IllegalArgument("No time specified");
        self._when = when

    def format(self,fmt=None):
        import time
        if not fmt:
            fmt = '%A, %B %d %Y @ %H:%M %Z'
        return time.strftime(fmt,time.localtime(self._when))

    def closestTimeTo(self,w):
        return time.localtime(self._when)

    def duration(self):
        return ''

    def diffFrom(self,when):
        return when - self._when

    def timeFits(self,time):
        return time == self._when

    def isValid(self,year,month,day):
        return time.localtime(self._when).tm_year == year and time.localtime(self._when).tm_mon == month

    def gmtime(self):
        return time.gmtime(self._when)

    def localtime(self):
        return time.localtime(self._when)

    def isToday(self):
        return time.localtime(self._when).tm_mday == time.localtime().tm_mday

    def __repr__(self):
        return 'Moment('+str(self._when)+')'
        return str(self._when)

    def __eq__(self,other):
        if isinstance(other,TimeRange):
            return other._start == self._when
        try:
            return other._when == self._when
        except AttributeError:
            return False

    def __cmp__(self,other):
        if isinstance(other,TimeRange):
            return cmp(self._when,other._start)
        try:
            return cmp(self._when,other._when)
        except AttributeError:
            return False


class Schedule(Time):
    """
    this class name sucks, but hey.
    """

    def __init__(self,*args):
        self._times = []
        for x in args:
            if isinstance(x,Time):
                self._times.append(x)


    def timeFits(self,time):
        """
        if "time" is inside any of the times in this schedule, then
        return True
        """
        for time in self._times:
            if time.timeFits(time):
                return True
        return False



class Repeating(Schedule):
    """
    this implements a repeating time (a Schedule can contain Schedules) using
    an arbitrary method of determining the next time...
    """

    def __init__(self,start,duration,generate,stop):
        if not isinstance(start,float):
            start = time.mktime(start)
        if not isinstance(stop,float):
            stop = time.mktime(stop)
        self._start = start
        self._stop = stop
        self._duration = duration
        self._method = generate

    def isWeekly(self):
        return isinstance(self._method,RepeatGenerateWeekly)

    def __str__(self):
        return "<Repeating time " + str(self._start) + ', ' + str(self._duration) + ', ' + str(self._method) + '>'

    def start(self):
        return time.mktime(self.getNextAfter(self._start))
    
    def stop(self):
        if not self._stop: return None
        print "STOP:",self._stop
        return self._stop

    def duration(self):
        hrmin = (int(self._duration/3600), int((self._duration%3600)/60))
        rtn = ''
        if hrmin[0] == 0 or hrmin[0] == 1:
            rtn = rtn + '%d hour' % hrmin[0]
        else:
            rtn = rtn + '%d hours' % hrmin[0]

        if hrmin[1] > 0:
            rtn = rtn + ' and %d minute' % hrmin[1]
            if hrmin[1] != 1:
                rtn = rtn + 's'
        return rtn

    def format(self,fmt=None):
        if fmt != None:
            # FIXME; this is for the editing stuff
            return formatTime(self.getNextAfter(time.time()),fmt)
                                        
        start = self.getNextAfter(self.start())
        start = time.localtime(self.start())
            
        rtn = [tags.span(style='font-weight:normal;')[self._method.format(fmt) + " starting " + formatTime(start,fmt) + "; "]]
        for x in range(Config.recurring_upcoming_days_to_show):
            start = time.mktime(start)
            start = self.getNextAfter(start)
            if not self.isValid(start.tm_year,start.tm_mon,start.tm_mday):
                rtn.append('...')
                break
            else:
                rtn.append(", ")
            if x == 0:
                rtn.append(tags.br)
                rtn.append("upcoming: ")
                rtn.append(formatTime(start,fmt))
            else:
                rtn.append(tags.span(style='font-weight:normal;')[formatTime(start,'%B %d, %Y')])
                
        return rtn

    def formatTextOnly(self,fmt=None):
        start = self.getNextAfter(self.start())
        start = time.localtime(self.start())
            
        rtn = self._method.format(fmt) + " starting " + formatTime(start,fmt)
        for x in range(Config.recurring_upcoming_days_to_show):
            start = time.mktime(start)
            start = self.getNextAfter(start)
            if not self.isValid(start.tm_year,start.tm_mon,start.tm_mday):
                rtn = rtn + '...'
                break
            if x == 0:
                rtn = rtn + ";\nupcoming: " + formatTime(start,fmt) + ', '
            else:
                rtn = rtn + formatTime(start,'%B %d, %Y') + ', '
                
        return rtn[:-2]

    def localtime(self):
        return time.localtime(self._start)
    
    def isValid(self, year, month, day):
        tmstart = time.localtime(self._start)
        tmstop = time.localtime(self._stop)
        if year < tmstart.tm_year:
            return False
        if year == tmstart.tm_year and month < tmstart.tm_mon:
            return False
        if year == tmstart.tm_year and month == tmstart.tm_mon:
            next = self.getNextAfter(self._start)
            if next.tm_mon != month:
                return False
        if year > tmstop.tm_year or month > tmstop.tm_mon:
            return False;
        if day:
            if year == tmstop.tm_year and month == tmstop.tm_mon:
                if day > tmstop.tm_mday:
                    print time.asctime(tmstop)
                    print "NO GOOD:",tmstop,"for y,m,d:",year,month,day
                    return False
        return True

    def timeFits(self,fit):
        current = self._start

        while current < fit:
            if current < fit and current+duration > fit:
                return True

            current = self._method(current,current)
        return False

    def closestTimeTo(self,after):
        return self.getNextAfter(after)

    def getNextAfter(self,after):
#        if after < self._start:
#            return None
        return self._method(self._start,after)
        

DAYS = ['monday','tuesday','wednesday','thursday','friday','saturday','sunday']
ADJECTIVES = ['first','second','third','fourth']

class RepeatGenerateMonthly:


    def __init__(self, adjective, day):
                                        # how often (e.g. "first", "third", etc.)
                                        # day is a weekday (e.g. "saturday")

        self.nth = ADJECTIVES.index(adjective.strip().lower()) + 1
        self.day = DAYS.index(day.strip().lower())

    def format(self,fmt=None):
        return "Every " + ADJECTIVES[self.nth-1] + " " + DAYS[self.day] + " of the month."

    def ifSkip(self,v):
        if self.nth == v:
            return {'selected':'selected'}
        return {}

    def ifDay(self,v):
        if self.day == v:
            return {'selected':'selected'}
        return {}

    def __call__(self, start, after):
        """ this will generate a new time after the one specified as start """
        new = time.localtime(start)
        start = time.localtime(after)
        currentmonth = start.tm_mon
        currentyear = start.tm_year

        currentmonth = currentmonth + 1
        if currentmonth > 12:
            currentmonth = 1
            currentyear = currentyear + 1

        nth = 0
        done = False
        (weekday,days) = calendar.monthrange(currentyear,currentmonth)
        for d in xrange(1,days+1):
            if calendar.weekday(currentyear,currentmonth,d) == self.day:
                nth = nth + 1
            if nth == self.nth:
                currentday = d
                done = True
                break

        if not done:
            raise "cannot find %d-th %s in month %d" % (self.nth, DAYS[self.day], currentmonth)
        
        # why no library method to do this?
        yday = 0
        for x in range(1,currentmonth):
            (weekday,days) = calendar.monthrange(currentyear,x)
            yday = yday + days
        yday = yday + currentday

        return time.struct_time(
            (currentyear,
             currentmonth,
             currentday,
             new.tm_hour,
             new.tm_min,
             new.tm_sec,
             self.day,
             yday,
             0)
            )
                                
        new.tm_mon = currentmonth
        new.tm_day = currentday
        new.tm_year = currentyear
        return new
        
#     def __call__(self, start, after):
#         """ this will generate a new time after the one specified as start """
#         new = time.localtime(start)
#         start = time.localtime(after)
#         currentmonth = start.tm_mon
#         currentyear = start.tm_year
#         done = False
#         currentday = None
#         while not done:
#             nth = 0
#             print currentyear, currentmonth
#             (weekday,days) = calendar.monthrange(currentyear,currentmonth)
#             for d in xrange(1,days+1):
#                 if calendar.weekday(currentyear,currentmonth,d) == self.day:
#                     nth = nth + 1
#                 if nth == self.nth:
#                     currentday = d
#                     if currentmonth > start.tm_mon:
#                         done = True
#                         break

#             if not done:
#                 currentmonth = currentmonth + 1
#                 if currentmonth > 12:
#                     currentmonth = 1
#                     currentyear = currentyear + 1

#         # why no library method to do this?
#         yday = 0
#         for x in range(1,currentmonth):
#             (weekday,days) = calendar.monthrange(currentyear,x)
#             yday = yday + days
#         yday = yday + currentday

#         return time.struct_time(
#             (currentyear,
#              currentmonth,
#              currentday,
#              new.tm_hour,
#              new.tm_min,
#              new.tm_sec,
#              self.day,
#              yday,
#              0)
#             )
                                
#         new.tm_mon = currentmonth
#         new.tm_day = currentday
#         new.tm_year = currentyear
#         return new
        

SKIPS = ['', 'second', 'third', 'fourth']
class RepeatGenerateWeekly:


    def __init__(self, skip, day):
                                        # skip is "", "second", "third", "fourth", ...
                                        # day is a weekday (e.g. "saturday")
        skip = skip.strip().lower()
        if skip == 'nothing' or skip == 'first':
            skip = ''
        self.skip = SKIPS.index(skip)
        self.day = DAYS.index(day.strip().lower())

    def __str__(self):
        return '<Weekly ' + str(self.skip) + ', ' + str(self.day) + '>'

    def ifSkip(self,v):
        if v == 0 and self.skip == 0: 
            return {'selected':'selected'}
        if self.skip == (v-1):
            return {'selected':'selected'}
        return {}

    def ifDay(self,v):
        if self.day == v:
            return {'selected':'selected'}
        return {}

    
    def format(self,fmt=None):
        if self.skip == 0:
            return "Every " + DAYS[self.day]
        return "Every " + SKIPS[self.skip] + " " + DAYS[self.day]


    def __call__(self, start, after):
        """ this will generate a new time after the one specified as start """
        #print "RepeateGenerateWeekly; start =",start,", after =",after,"skip=",self.skip
        new = time.localtime(start)
        start = time.localtime(start)
        target = time.localtime(after)
        #print "  target:",target.tm_year,target.tm_mon
        currentmonth = start.tm_mon
        currentyear = start.tm_year
        currentday = start.tm_mday+1
        done = False
        nth = 0
        while not done and currentyear < 2010:
            (weekday,days) = calendar.monthrange(currentyear,currentmonth)
            for d in xrange(currentday,days+1):
                currentday = d
                if calendar.weekday(currentyear,currentmonth,d) == self.day:
                    if nth == self.skip:
                        if currentyear >= target.tm_year:
                            if currentmonth > target.tm_mon or (currentmonth == target.tm_mon and currentday >= target.tm_mday+1):
                                #print "done!",currentyear,currentmonth,currentday
                                done = True
                                break
                        #print "  ",currentyear,currentmonth,currentday,"NOT good",target.tm_year,target.tm_mon,target.tm_mday
                        nth = 0
                    else:
                        nth = nth + 1
                    #print "  nth:",nth

                # reset; we've crossed to the next month
            if not done:
                currentday = 1
                currentmonth = currentmonth + 1
                #print "  month:",currentmonth
                if currentmonth > 12:
                    currentmonth = 1
                    currentyear = currentyear + 1
                    #print "  year:",currentyear

        # why no library method to do this?
        yday = 0
        for x in range(1,currentmonth):
            (weekday,days) = calendar.monthrange(currentyear,x)
            yday = yday + days
        yday = yday + currentday

        return time.struct_time(
            (currentyear,
             currentmonth,
             currentday,
             new.tm_hour,
             new.tm_min,
             new.tm_sec,
             self.day,
             yday,
             0)
            )
                
            


def now(arg=None):
    if arg is None:
        return Moment(time.time())
    return TimeRange(time.time(),arg)

def everyMonth(current):
    # increment month and return...
    pass
